#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
System Monitoring Module
Tracks performance metrics and resource usage
"""

import logging
import time
import psutil
import threading
from typing import Dict, List, Any, Optional
from datetime import datetime

logger = logging.getLogger("monitoring")

class SystemMonitor:
    """System Monitoring Class"""
    
    def __init__(self):
        """Initialize system monitor"""
        self.metrics = {
            'cpu_usage': [],
            'memory_usage': [],
            'gpu_usage': [],  # Will be populated if GPU is available
            'disk_io': [],
            'network_io': [],
            'process_metrics': [],
            'video_processing': []
        }
        self.max_history_size = 3600  # 1 hour of data at 1 second intervals
        self.monitoring_thread = None
        self.running = False
        self.monitor_interval = 1.0  # seconds
        self.lock = threading.RLock()
        self.start_time = time.time()
        self.last_disk_io = psutil.disk_io_counters()
        self.last_network_io = psutil.net_io_counters()
        self.last_io_time = time.time()
        
        # Try to import GPU monitoring if available
        self.gpu_available = False
        try:
            import GPUtil
            self.gpu_available = True
            self.gputil = GPUtil
            logger.info("GPU monitoring available")
        except ImportError:
            logger.warning("GPU monitoring not available: GPUtil not installed")
        
        logger.info("System monitor initialized")
    
    def start_monitoring(self, interval: float = 1.0):
        """Start monitoring in a background thread
        
        Args:
            interval: Monitoring interval in seconds
        """
        try:
            if self.running:
                logger.warning("Monitoring already running")
                return False
            
            self.monitor_interval = interval
            self.running = True
            self.monitoring_thread = threading.Thread(target=self._monitor_loop, daemon=True)
            self.monitoring_thread.start()
            logger.info(f"Started system monitoring with interval {interval}s")
            return True
            
        except Exception as e:
            logger.error(f"Error starting monitoring: {str(e)}")
            self.running = False
            return False
    
    def stop_monitoring(self):
        """Stop monitoring"""
        try:
            if not self.running:
                logger.warning("Monitoring not running")
                return False
            
            self.running = False
            if self.monitoring_thread:
                self.monitoring_thread.join(timeout=5.0)
            
            logger.info("Stopped system monitoring")
            return True
            
        except Exception as e:
            logger.error(f"Error stopping monitoring: {str(e)}")
            return False
    
    def _monitor_loop(self):
        """Main monitoring loop"""
        while self.running:
            try:
                # Collect system metrics
                metrics = self._collect_system_metrics()
                
                # Store metrics
                with self.lock:
                    self._update_metrics(metrics)
                
                # Sleep for monitoring interval
                time.sleep(self.monitor_interval)
                
            except Exception as e:
                logger.error(f"Error in monitoring loop: {str(e)}")
                time.sleep(max(1.0, self.monitor_interval))
    
    def _collect_system_metrics(self) -> Dict[str, Any]:
        """Collect system metrics"""
        current_time = time.time()
        
        # CPU usage
        cpu_percent = psutil.cpu_percent(interval=0.1)
        
        # Memory usage
        memory = psutil.virtual_memory()
        memory_percent = memory.percent
        memory_used_mb = memory.used / (1024 * 1024)
        
        # GPU usage
        gpu_metrics = []
        if self.gpu_available:
            try:
                gpus = self.gputil.getGPUs()
                for gpu in gpus:
                    gpu_metrics.append({
                        'id': gpu.id,
                        'name': gpu.name,
                        'load': gpu.load * 100,
                        'memory_used': gpu.memoryUsed,
                        'memory_total': gpu.memoryTotal,
                        'temperature': gpu.temperature
                    })
            except Exception as e:
                logger.error(f"Error collecting GPU metrics: {str(e)}")
        
        # Disk I/O
        disk_io = psutil.disk_io_counters()
        time_diff = current_time - self.last_io_time
        
        disk_read_mbps = ((disk_io.read_bytes - self.last_disk_io.read_bytes) / time_diff) / (1024 * 1024)
        disk_write_mbps = ((disk_io.write_bytes - self.last_disk_io.write_bytes) / time_diff) / (1024 * 1024)
        
        # Network I/O
        network_io = psutil.net_io_counters()
        network_recv_mbps = ((network_io.bytes_recv - self.last_network_io.bytes_recv) / time_diff) / (1024 * 1024)
        network_sent_mbps = ((network_io.bytes_sent - self.last_network_io.bytes_sent) / time_diff) / (1024 * 1024)
        
        # Update last values
        self.last_disk_io = disk_io
        self.last_network_io = network_io
        self.last_io_time = current_time
        
        # Process metrics
        process = psutil.Process()
        with process.oneshot():
            process_cpu_percent = process.cpu_percent(interval=0.1)
            process_memory_percent = process.memory_percent()
            process_memory_mb = process.memory_info().rss / (1024 * 1024)
            process_threads = len(process.threads())
            
        # Uptime
        uptime_seconds = current_time - self.start_time
        
        return {
            'timestamp': current_time,
            'cpu': {
                'percent': cpu_percent,
                'per_core': psutil.cpu_percent(interval=0.1, percpu=True)
            },
            'memory': {
                'percent': memory_percent,
                'used_mb': memory_used_mb,
                'total_mb': memory.total / (1024 * 1024)
            },
            'gpu': gpu_metrics,
            'disk_io': {
                'read_mbps': disk_read_mbps,
                'write_mbps': disk_write_mbps
            },
            'network_io': {
                'recv_mbps': network_recv_mbps,
                'sent_mbps': network_sent_mbps
            },
            'process': {
                'cpu_percent': process_cpu_percent,
                'memory_percent': process_memory_percent,
                'memory_mb': process_memory_mb,
                'threads': process_threads
            },
            'uptime_seconds': uptime_seconds
        }
    
    def _update_metrics(self, metrics: Dict[str, Any]):
        """Update metrics storage"""
        timestamp = metrics['timestamp']
        
        # Update CPU metrics
        self.metrics['cpu_usage'].append({
            'timestamp': timestamp,
            'value': metrics['cpu']['percent']
        })
        
        # Update memory metrics
        self.metrics['memory_usage'].append({
            'timestamp': timestamp,
            'value': metrics['memory']['percent'],
            'used_mb': metrics['memory']['used_mb']
        })
        
        # Update GPU metrics if available
        if metrics['gpu']:
            for gpu in metrics['gpu']:
                self.metrics['gpu_usage'].append({
                    'timestamp': timestamp,
                    'gpu_id': gpu['id'],
                    'load': gpu['load'],
                    'memory_used': gpu['memory_used'],
                    'temperature': gpu['temperature']
                })
        
        # Update disk I/O metrics
        self.metrics['disk_io'].append({
            'timestamp': timestamp,
            'read_mbps': metrics['disk_io']['read_mbps'],
            'write_mbps': metrics['disk_io']['write_mbps']
        })
        
        # Update network I/O metrics
        self.metrics['network_io'].append({
            'timestamp': timestamp,
            'recv_mbps': metrics['network_io']['recv_mbps'],
            'sent_mbps': metrics['network_io']['sent_mbps']
        })
        
        # Update process metrics
        self.metrics['process_metrics'].append({
            'timestamp': timestamp,
            'cpu_percent': metrics['process']['cpu_percent'],
            'memory_percent': metrics['process']['memory_percent'],
            'memory_mb': metrics['process']['memory_mb'],
            'threads': metrics['process']['threads']
        })
        
        # Trim history
        for key in self.metrics:
            if len(self.metrics[key]) > self.max_history_size:
                self.metrics[key] = self.metrics[key][-self.max_history_size:]
    
    def record_video_processing_metrics(self, camera_id: str, 
                                     fps: float, 
                                     processing_time_ms: float,
                                     detected_objects: int,
                                     errors: int = 0):
        """Record video processing metrics
        
        Args:
            camera_id: Camera identifier
            fps: Frames per second
            processing_time_ms: Processing time per frame in milliseconds
            detected_objects: Number of detected objects
            errors: Number of errors
        """
        with self.lock:
            self.metrics['video_processing'].append({
                'timestamp': time.time(),
                'camera_id': camera_id,
                'fps': fps,
                'processing_time_ms': processing_time_ms,
                'detected_objects': detected_objects,
                'errors': errors
            })
            
            # Trim history
            if len(self.metrics['video_processing']) > self.max_history_size:
                self.metrics['video_processing'] = self.metrics['video_processing'][-self.max_history_size:]
    
    def get_current_metrics(self) -> Dict[str, Any]:
        """Get current system metrics
        
        Returns:
            Current system metrics
        """
        # Collect fresh metrics
        return self._collect_system_metrics()
    
    def get_metrics_history(self, metric_type: str, 
                          duration_seconds: int = 60) -> List[Dict]:
        """Get metrics history for specific type
        
        Args:
            metric_type: Metric type to retrieve
            duration_seconds: Duration in seconds
            
        Returns:
            List of metrics
        """
        if metric_type not in self.metrics:
            logger.warning(f"Unknown metric type: {metric_type}")
            return []
        
        with self.lock:
            cutoff_time = time.time() - duration_seconds
            return [
                metric for metric in self.metrics[metric_type] 
                if metric['timestamp'] >= cutoff_time
            ]
    
    def get_average_metrics(self, duration_seconds: int = 60) -> Dict[str, Any]:
        """Get average metrics for the specified duration
        
        Args:
            duration_seconds: Duration in seconds
            
        Returns:
            Dictionary of average metrics
        """
        with self.lock:
            cutoff_time = time.time() - duration_seconds
            
            # Filter metrics within time window
            cpu_metrics = [m for m in self.metrics['cpu_usage'] if m['timestamp'] >= cutoff_time]
            memory_metrics = [m for m in self.metrics['memory_usage'] if m['timestamp'] >= cutoff_time]
            disk_metrics = [m for m in self.metrics['disk_io'] if m['timestamp'] >= cutoff_time]
            network_metrics = [m for m in self.metrics['network_io'] if m['timestamp'] >= cutoff_time]
            process_metrics = [m for m in self.metrics['process_metrics'] if m['timestamp'] >= cutoff_time]
            video_metrics = [m for m in self.metrics['video_processing'] if m['timestamp'] >= cutoff_time]
            
            # Calculate averages
            avg_cpu = sum(m['value'] for m in cpu_metrics) / len(cpu_metrics) if cpu_metrics else 0
            avg_memory = sum(m['value'] for m in memory_metrics) / len(memory_metrics) if memory_metrics else 0
            avg_disk_read = sum(m['read_mbps'] for m in disk_metrics) / len(disk_metrics) if disk_metrics else 0
            avg_disk_write = sum(m['write_mbps'] for m in disk_metrics) / len(disk_metrics) if disk_metrics else 0
            avg_network_recv = sum(m['recv_mbps'] for m in network_metrics) / len(network_metrics) if network_metrics else 0
            avg_network_sent = sum(m['sent_mbps'] for m in network_metrics) / len(network_metrics) if network_metrics else 0
            avg_process_cpu = sum(m['cpu_percent'] for m in process_metrics) / len(process_metrics) if process_metrics else 0
            avg_process_memory = sum(m['memory_percent'] for m in process_metrics) / len(process_metrics) if process_metrics else 0
            
            # Calculate video processing metrics
            video_stats = {}
            if video_metrics:
                # Group by camera
                camera_metrics = {}
                for metric in video_metrics:
                    camera_id = metric['camera_id']
                    if camera_id not in camera_metrics:
                        camera_metrics[camera_id] = []
                    camera_metrics[camera_id].append(metric)
                
                # Calculate per-camera stats
                for camera_id, metrics in camera_metrics.items():
                    avg_fps = sum(m['fps'] for m in metrics) / len(metrics)
                    avg_processing_time = sum(m['processing_time_ms'] for m in metrics) / len(metrics)
                    total_objects = sum(m['detected_objects'] for m in metrics)
                    total_errors = sum(m['errors'] for m in metrics)
                    
                    video_stats[camera_id] = {
                        'avg_fps': avg_fps,
                        'avg_processing_time_ms': avg_processing_time,
                        'total_objects': total_objects,
                        'total_errors': total_errors,
                        'error_rate': total_errors / len(metrics) if metrics else 0
                    }
            
            return {
                'system': {
                    'avg_cpu_percent': round(avg_cpu, 2),
                    'avg_memory_percent': round(avg_memory, 2),
                    'avg_disk_read_mbps': round(avg_disk_read, 2),
                    'avg_disk_write_mbps': round(avg_disk_write, 2),
                    'avg_network_recv_mbps': round(avg_network_recv, 2),
                    'avg_network_sent_mbps': round(avg_network_sent, 2)
                },
                'process': {
                    'avg_cpu_percent': round(avg_process_cpu, 2),
                    'avg_memory_percent': round(avg_process_memory, 2)
                },
                'video_processing': video_stats,
                'time_window_seconds': duration_seconds,
                'sample_count': {
                    'cpu': len(cpu_metrics),
                    'memory': len(memory_metrics),
                    'disk': len(disk_metrics),
                    'network': len(network_metrics),
                    'process': len(process_metrics),
                    'video': len(video_metrics)
                }
            }
    
    def check_health(self) -> Dict[str, Any]:
        """Check system health and return status
        
        Returns:
            System health status
        """
        current_metrics = self.get_current_metrics()
        
        # Define thresholds
        thresholds = {
            'cpu_critical': 90,
            'cpu_warning': 75,
            'memory_critical': 90,
            'memory_warning': 75,
            'gpu_critical': 90,
            'gpu_warning': 75,
            'process_cpu_critical': 80,
            'process_memory_critical': 50
        }
        
        # Check CPU health
        cpu_status = 'healthy'
        if current_metrics['cpu']['percent'] >= thresholds['cpu_critical']:
            cpu_status = 'critical'
        elif current_metrics['cpu']['percent'] >= thresholds['cpu_warning']:
            cpu_status = 'warning'
        
        # Check memory health
        memory_status = 'healthy'
        if current_metrics['memory']['percent'] >= thresholds['memory_critical']:
            memory_status = 'critical'
        elif current_metrics['memory']['percent'] >= thresholds['memory_warning']:
            memory_status = 'warning'
        
        # Check GPU health
        gpu_status = 'healthy'
        gpu_warnings = []
        if current_metrics['gpu']:
            for gpu in current_metrics['gpu']:
                if gpu['load'] >= thresholds['gpu_critical']:
                    gpu_status = 'critical'
                    gpu_warnings.append(f"GPU {gpu['id']} load: {gpu['load']:.1f}%")
                elif gpu['load'] >= thresholds['gpu_warning']:
                    if gpu_status != 'critical':
                        gpu_status = 'warning'
                    gpu_warnings.append(f"GPU {gpu['id']} load: {gpu['load']:.1f}%")
        
        # Check process health
        process_status = 'healthy'
        if (current_metrics['process']['cpu_percent'] >= thresholds['process_cpu_critical'] or
            current_metrics['process']['memory_percent'] >= thresholds['process_memory_critical']):
            process_status = 'critical'
        
        # Overall status (worst of all components)
        status_priority = {'healthy': 0, 'warning': 1, 'critical': 2}
        overall_status = 'healthy'
        component_statuses = [cpu_status, memory_status, gpu_status, process_status]
        
        for status in component_statuses:
            if status_priority[status] > status_priority[overall_status]:
                overall_status = status
        
        return {
            'status': overall_status,
            'components': {
                'cpu': {
                    'status': cpu_status,
                    'value': current_metrics['cpu']['percent'],
                    'threshold_warning': thresholds['cpu_warning'],
                    'threshold_critical': thresholds['cpu_critical']
                },
                'memory': {
                    'status': memory_status,
                    'value': current_metrics['memory']['percent'],
                    'threshold_warning': thresholds['memory_warning'],
                    'threshold_critical': thresholds['memory_critical']
                },
                'gpu': {
                    'status': gpu_status,
                    'warnings': gpu_warnings
                },
                'process': {
                    'status': process_status,
                    'cpu_percent': current_metrics['process']['cpu_percent'],
                    'memory_percent': current_metrics['process']['memory_percent']
                }
            },
            'timestamp': current_metrics['timestamp'],
            'uptime_seconds': current_metrics['uptime_seconds']
        }

# Create singleton instance for easy import
def get_system_monitor() -> SystemMonitor:
    """Get system monitor instance
    
    Returns:
        SystemMonitor instance
    """
    if not hasattr(get_system_monitor, '_instance'):
        get_system_monitor._instance = SystemMonitor()
    return get_system_monitor._instance